#include "Node.h"

#include "Exception.h"

void CHtmlNode::Expose(void)
{
  py::class_<CHtmlNode>("HtmlNode", py::no_init)
    .add_property("parent", &CHtmlNode::GetParent, &CHtmlNode::SetParent)
    .add_property("alive", &CHtmlNode::IsAlive)
    .add_property("rewritable", &CHtmlNode::IsRewritable)

    .def("__nonzero__", &CHtmlNode::IsAlive)
    .def("__cmp__", &CHtmlNode::Compare)
    ;

  py::class_<CHtmlLeafNode, py::bases<CHtmlNode> >("HtmlLeafNode", py::no_init)
    ;

  py::class_<CHtmlCdataNode, py::bases<CHtmlLeafNode> >("HtmlCdataNode", py::no_init)
    .add_property("contents", &CHtmlCdataNode::GetContents)
    ;

  py::class_<CHtmlCharactersNode, py::bases<CHtmlLeafNode> >("HtmlCharactersNode", py::no_init)
    .add_property("contents", &CHtmlCharactersNode::GetContents)
    .def("append", &CHtmlCharactersNode::Append)
    ;

  py::class_<CHtmlCommentNode, py::bases<CHtmlLeafNode> >("HtmlCommentNode", py::no_init)
    .add_property("contents", &CHtmlCommentNode::GetContents)
    ;

  py::class_<CHtmlIEDirectiveNode, py::bases<CHtmlLeafNode> >("HtmlIEDirectiveNode", py::no_init)
    .add_property("contents", &CHtmlIEDirectiveNode::GetContents)
    ;

  py::class_<CHtmlDirectiveNode, py::bases<CHtmlLeafNode> >("HtmlDirectiveNode", py::no_init)
    .add_property("contents", &CHtmlDirectiveNode::GetContents)
    ;

  py::enum_<HtmlElement::CloseStyle>("CloseStyle")
    .value("auto", HtmlElement::AUTO_CLOSE)          // synthesized tag, or not yet closed in source
    .value("implicit", HtmlElement::IMPLICIT_CLOSE)  // E.g. <img...> <meta...> <link...> <br...> <input...>
    .value("explicit", HtmlElement::EXPLICIT_CLOSE)  // E.g. <a href=...>anchor</a>
    .value("brief", HtmlElement::BRIEF_CLOSE)        // E.g. <head/>
    .value("unclosed", HtmlElement::UNCLOSED)        // Was never closed in source
    ;

  py::class_<CHtmlAttribute>("HtmlAttribute", py::no_init)
    .add_property("parent", &CHtmlAttribute::GetParent)

    .add_property("name", &CHtmlAttribute::GetName, &CHtmlAttribute::SetName)
    .add_property("value", &CHtmlAttribute::GetValue, &CHtmlAttribute::SetValue)
    .add_property("escapedValue", &CHtmlAttribute::GetEscapedValue, &CHtmlAttribute::SetEscapedValue)
    .add_property("quote", &CHtmlAttribute::GetQuote, &CHtmlAttribute::SetQuote)

    .def("__cmp__", &CHtmlAttribute::Compare)
    ;

  void (CHtmlElement::*fnCloneAttribute)(const CHtmlAttribute&) = &CHtmlElement::AddAttribute;
  void (CHtmlElement::*fnAddAttribute)(const std::string&, const std::string&, const std::string) = &CHtmlElement::AddAttribute;
  void (CHtmlElement::*fnAddIntAttribute)(const std::string&, int) = &CHtmlElement::AddAttribute;

  void (CHtmlElement::*fnDeleteIntAttribute)(int) = &CHtmlElement::DeleteAttribute;
  void (CHtmlElement::*fnDeleteAttribute)(const std::string&) = &CHtmlElement::DeleteAttribute;

  py::class_<CHtmlElement, py::bases<CHtmlNode> >("HtmlElement", py::no_init)
    .add_property("tagName", &CHtmlElement::GetTagName, &CHtmlElement::SetTagName)
    .add_property("closeStyle", &CHtmlElement::GetCloseStyle, &CHtmlElement::SetCloseStyle)
    .add_property("beginLineNum", &CHtmlElement::GetBeginLineNumber)
    .add_property("endLineNum", &CHtmlElement::GetEndLineNumber)

    .def("addAttribute", fnCloneAttribute, py::args("attr"))
    .def("addAttribute", fnAddAttribute, (py::arg("name"), py::arg("value"), py::arg("quote") = "\""))
    .def("addAttribute", fnAddIntAttribute, py::args("name", "value"))
    .def("addEscapedAttribute", fnAddAttribute, (py::arg("name"), py::arg("value"), py::arg("quote") = "\""))

    .def("deleteAttribute", fnDeleteIntAttribute, py::args("index"))
    .def("deleteAttribute", fnDeleteAttribute, py::args("name"))

    .def("findAttribute", &CHtmlElement::FindAttribute, py::args("name"))
    .def("getAttributeValue", &CHtmlElement::GetAttributeValue, py::args("name"))

    .add_property("attributeCount", &CHtmlElement::GetAttributeSize)
    .def("getAttribute", &CHtmlElement::GetAttribute, py::args("index"))

    .def("__len__", &CHtmlElement::GetAttributeSize)
    .def("__getitem__", &CHtmlElement::GetItem)
    .def("__setitem__", &CHtmlElement::SetItem)
    .def("__delitem__", &CHtmlElement::DelItem)
    .def("__contains__", &CHtmlElement::Contains)

    .def("prependChild", &CHtmlElement::PrependChild, py::args("newChild"))
    .def("appendChild", &CHtmlElement::AppendChild, py::args("newChild"))
    ;

  py::class_<CHtmlDocument>("HtmlDocument", py::no_init)
    .def("createCDATASection", &CHtmlDocument::NewCdataNode, (py::arg("contents"), py::arg("parent") = py::object()))
    .def("createTextNode", &CHtmlDocument::NewCharactersNode, (py::arg("literal"), py::arg("parent") = py::object()))
    .def("createComment", &CHtmlDocument::NewCommentNode, (py::arg("contents"), py::arg("parent") = py::object()))
    .def("createDirective", &CHtmlDocument::NewDirectiveNode, (py::arg("contents"), py::arg("parent") = py::object()))
    .def("createIEDirective", &CHtmlDocument::NewIEDirectiveNode, (py::arg("contents"), py::arg("parent") = py::object()))
    .def("createElement", &CHtmlDocument::NewElement, (py::arg("tag"), py::arg("parent") = py::object()))

    .def("addNode", &CHtmlDocument::AddCdataNode, (py::arg("node"), py::arg("linenum")=-1))
    .def("addNode", &CHtmlDocument::AddCharactersNode, (py::arg("node"), py::arg("linenum")=-1))
    .def("addNode", &CHtmlDocument::AddCommentNode, (py::arg("node"), py::arg("linenum")=-1))
    .def("addNode", &CHtmlDocument::AddDirectiveNode, (py::arg("node"), py::arg("linenum")=-1))
    .def("addNode", &CHtmlDocument::AddIEDirectiveNode, (py::arg("node"), py::arg("linenum")=-1))

    .def("addElement", &CHtmlDocument::AddElement, (py::arg("element"), py::arg("linenum")=-1))
    .def("closeElement", &CHtmlDocument::CloseElement, (py::arg("element"), py::arg("linenum")=-1, py::arg("closestyle")=HtmlElement::EXPLICIT_CLOSE))

    .def("deleteNode", &CHtmlDocument::DeleteNode, py::args("node"))
    .def("deleteElemnt", &CHtmlDocument::DeleteElement, (py::arg("element"), py::arg("deep")=false))
    .def("replaceNode", &CHtmlDocument::ReplaceNode, py::args("oldNode", "newNode"))
    .def("clear", &CHtmlDocument::Clear)

    .def("insertBefore", &CHtmlDocument::InsertNodeBeforeNode, py::args("newChild", "refChild"))
    .def("insertAfter", &CHtmlDocument::InsertNodeAfterNode, py::args("newChild", "refChild"))
    .def("insertBefore", &CHtmlDocument::InsertNodeBeforeCurrent, py::args("newChild"))
    .def("insertAfter", &CHtmlDocument::InsertNodeAfterCurrent, py::args("newChild"))
    ;
}

py::object CHtmlNode::GetParent(void) const
{ 
  HtmlElement *parent = m_node->parent();

  if (parent == NULL) return py::object();

  CHtmlElement element(m_parse, m_node->parent());

  return py::object(py::handle<>(py::to_python_value<CHtmlElement&>()(element)));
}

void CHtmlNode::SetParent(CHtmlElement& parent) 
{ 
  HtmlTestingPeer::SetNodeParent(m_node, parent.GetElement()); 
}

int CHtmlNode::Compare(py::object other)
{
  py::extract<CHtmlNode&> node(other);

  if (node.check())  
    return (size_t) m_node - (size_t) node().m_node;
  
  return 1;
}

py::object CHtmlAttribute::GetParent(void) const 
{ 
  return py::object(py::handle<>(py::to_python_value<CHtmlElement&>()(m_element)));
}

void CHtmlAttribute::SetName(const std::string& name) 
{ 
  m_attr.set_name(m_element.GetParse()->Intern(name)); 
}

int CHtmlAttribute::Compare(py::object other)
{
  py::extract<CHtmlAttribute&> attr(other);

  if (attr.check())  
    return (size_t) &m_attr - (size_t) &attr().m_attr;

  return 1;
}

py::object CHtmlElement::FindAttribute(const std::string& name) 
{
  HtmlElement::Attribute* attr = static_cast<HtmlElement *>(m_node)->FindAttribute(m_parse->Intern(name));
  
  if (!attr) 
    return py::object();
    
  CHtmlAttribute attribute(*this, *attr);

  return py::object(py::handle<>(py::to_python_value<CHtmlAttribute&>()(attribute)));    
}

py::object CHtmlElement::GetAttribute(int index) 
{ 
  HtmlElement *element = static_cast<HtmlElement *>(m_node);

  if (index < 0 || index >= element->attribute_size())  
    return py::object();  
  
  CHtmlAttribute attr(*this, element->attribute(index));

  return py::object(py::handle<>(py::to_python_value<CHtmlAttribute&>()(attr)));
}

py::object CHtmlElement::GetAttributeValue(const std::string& name)
{
  const char *value = static_cast<HtmlElement *>(m_node)->AttributeValue(m_parse->Intern(name));

  return value ? py::str(value) : py::object();
}

const std::string CHtmlElement::ToString(py::object obj)
{
  if (PyString_CheckExact(obj.ptr()))
  {
    return std::string(PyString_AS_STRING(obj.ptr()), PyString_GET_SIZE(obj.ptr()));
  }
  
  if (PyUnicode_CheckExact(obj.ptr()))
  {
    py::object s(py::handle<>(PyUnicode_AsUTF8String(obj.ptr())));

    return std::string(PyString_AS_STRING(s.ptr()), PyString_GET_SIZE(s.ptr()));
  }

  return py::extract<std::string>(py::object(py::handle<>(::PyObject_Str(obj.ptr()))));
}

void CHtmlElement::GuessKey(py::object key, int& index, std::string& name)
{
  if (PyInt_CheckExact(key.ptr()))
  {
    index = PyInt_AS_LONG(key.ptr());    
  }
  else if (PyLong_CheckExact(key.ptr()))
  {
    index = ::PyLong_AsLong(key.ptr());
  }
  else if (PyString_CheckExact(key.ptr()))
  {
    name = std::string(PyString_AS_STRING(key.ptr()), PyString_GET_SIZE(key.ptr()));
  }
  else if (PyUnicode_CheckExact(key.ptr()))
  {
    py::object s(py::handle<>(PyUnicode_AsUTF8String(key.ptr())));

    name = std::string(PyString_AS_STRING(s.ptr()), PyString_GET_SIZE(s.ptr()));
  }
  else
  {
    py::object s(py::handle<>(::PyObject_Str(key.ptr())));

    name = std::string(PyString_AS_STRING(s.ptr()), PyString_GET_SIZE(s.ptr()));
  }
}

py::object CHtmlElement::GetItem(py::object key)
{
  int index = -1;
  std::string name;

  GuessKey(key, index, name);

  if (name.empty())
  {
    HtmlElement *element = static_cast<HtmlElement *>(m_node);

    if (index < 0 || index >= element->attribute_size())  
      return py::object();  

    return py::str(element->attribute(index).value());
  }
  else
  {
    return GetAttributeValue(name);
  }
}

void CHtmlElement::SetItem(py::object key, py::object value)
{
  int index = -1;
  std::string name;

  GuessKey(key, index, name);

  HtmlElement *element = static_cast<HtmlElement *>(m_node);  

  if (name.empty())
  {
    if (index < 0 || index >= element->attribute_size())  
    {
      throw IndexError("list index out of range");
    }
    else
    {
      element->attribute(index).SetValue(ToString(value));
    }
  }
  else
  {
    HtmlElement::Attribute* attr = element->FindAttribute(m_parse->Intern(name));

    if (attr)
    {
      attr->SetValue(ToString(value));
    }
    else if (PyInt_CheckExact(value.ptr()))
    {
      element->AddAttribute(m_parse->Intern(name), PyInt_AS_LONG(value.ptr()));
    }
    else if (PyLong_CheckExact(value.ptr()))
    {
      element->AddAttribute(m_parse->Intern(name), ::PyLong_AsLong(value.ptr()));
    }
    else
    {
      element->AddAttribute(m_parse->Intern(name), ToString(value), "\"");
    }
  }
}

void CHtmlElement::DelItem(py::object key)
{
  int index = -1;
  std::string name;

  GuessKey(key, index, name);

  HtmlElement *element = static_cast<HtmlElement *>(m_node);  

  if (name.empty())
  {
    if (index < 0 || index >= element->attribute_size())  
    {
      throw IndexError("list index out of range");
    }
    else
    {
      element->DeleteAttribute(index);
    }    
  }
  else
  {
    if (!element->DeleteAttribute(m_parse->Intern(name)))
    {
      throw KeyError(name);
    }
  }
}

bool CHtmlElement::Contains(py::object key)
{
  HtmlElement *element = static_cast<HtmlElement *>(m_node);  

  return element->FindAttribute(m_parse->Intern(ToString(key)));
}

void CHtmlElement::PrependChild(CHtmlNode& newChild)
{
  m_parse->PrependChild(GetElement(), newChild.GetNode());
} 
void CHtmlElement::AppendChild(CHtmlNode& newChild)
{
  m_parse->AppendChild(GetElement(), newChild.GetNode());
}

CHtmlCdataNode CHtmlDocument::NewCdataNode(const std::string& contents, py::object parent)
{
  py::extract<CHtmlElement &> element(parent);

  return CHtmlCdataNode(m_parse, m_parse->NewCdataNode(element.check() ? element().GetElement() : NULL, contents));
}

CHtmlCharactersNode CHtmlDocument::NewCharactersNode(const std::string& literal, py::object parent)
{
  py::extract<CHtmlElement &> element(parent);

  return CHtmlCharactersNode(m_parse, m_parse->NewCharactersNode(element.check() ? element().GetElement() : NULL, literal));
}

CHtmlCommentNode CHtmlDocument::NewCommentNode(const std::string& contents, py::object parent)
{
  py::extract<CHtmlElement &> element(parent);

  return CHtmlCommentNode(m_parse, m_parse->NewCommentNode(element.check() ? element().GetElement() : NULL, contents));
}

CHtmlDirectiveNode CHtmlDocument::NewDirectiveNode(const std::string& contents, py::object parent)
{
  py::extract<CHtmlElement &> element(parent);

  return CHtmlDirectiveNode(m_parse, m_parse->NewDirectiveNode(element.check() ? element().GetElement() : NULL, contents));
}

CHtmlIEDirectiveNode CHtmlDocument::NewIEDirectiveNode(const std::string& contents, py::object parent)
{
  py::extract<CHtmlElement &> element(parent);

  return CHtmlIEDirectiveNode(m_parse, m_parse->NewIEDirectiveNode(element.check() ? element().GetElement() : NULL, contents));
}

CHtmlElement CHtmlDocument::NewElement(const std::string tag, py::object parent)
{
  py::extract<CHtmlElement &> element(parent);

  return CHtmlElement(m_parse, m_parse->NewElement(element.check() ? element().GetElement() : NULL, m_parse->Intern(tag)));
}

CHtmlCdataNode CHtmlDocument::AddCdataNode(CHtmlCdataNode& node, int linenum)
{
  HtmlTestingPeer::AddEvent(m_parse, new HtmlCdataEvent(static_cast<HtmlCdataNode *>(node.GetNode()), linenum));

  return node;
}

CHtmlCharactersNode CHtmlDocument::AddCharactersNode(CHtmlCharactersNode& node, int linenum)
{
  HtmlTestingPeer::AddEvent(m_parse, new HtmlCharactersEvent(static_cast<HtmlCharactersNode *>(node.GetNode()), linenum));

  return node;
}
CHtmlCommentNode CHtmlDocument::AddCommentNode(CHtmlCommentNode& node, int linenum)
{
  HtmlTestingPeer::AddEvent(m_parse, new HtmlCommentEvent(static_cast<HtmlCommentNode *>(node.GetNode()), linenum));

  return node;
}

CHtmlDirectiveNode CHtmlDocument::AddDirectiveNode(CHtmlDirectiveNode& node, int linenum)
{
  HtmlTestingPeer::AddEvent(m_parse, new HtmlDirectiveEvent(static_cast<HtmlDirectiveNode *>(node.GetNode()), linenum));

  return node;
}

CHtmlIEDirectiveNode CHtmlDocument::AddIEDirectiveNode(CHtmlIEDirectiveNode& node, int linenum)
{
  HtmlTestingPeer::AddEvent(m_parse, new HtmlIEDirectiveEvent(static_cast<HtmlIEDirectiveNode *>(node.GetNode()), linenum));

  return node;
}

CHtmlElement CHtmlDocument::AddElement(CHtmlElement& element, int linenum)
{
  m_parse->AddElement(element.GetElement(), linenum);

  return element;
}

CHtmlElement CHtmlDocument::CloseElement(CHtmlElement& element, int linenum, HtmlElement::CloseStyle style)
{
  m_parse->CloseElement(element.GetElement(), style, linenum);

  return element;
}